/*
 * Created by JFormDesigner on Wed Oct 05 22:47:36 PDT 2011
 */

package com.thirdnf.calendar;

import javax.swing.*;

import com.jgoodies.forms.factories.*;
import com.jgoodies.forms.layout.*;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.joda.time.LocalDate;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;


/**
 *
 * @author Joshua Gerth - jgerth@thirdnf.com
 */
@SuppressWarnings({"FieldCanBeLocal"})
public class Month extends JPanel
{
    @SuppressWarnings({"UnusedDeclaration"})
    private static final Logger logger = Logger.getLogger(DateField.class);

    // List of listeners who want to know when a date has been clicked.
    private final List<PropertyChangeListener> _propertyChangeListeners;

    // This is the date they have selected, or have set
    private LocalDate _selectedDate;
    private DayOfMonth _selectedWidget;

    // This is today's date which is colored red and we need to be able to determine if the date has changed.
    private LocalDate _todayDate;

    private int _monthVisible;
    private int _yearVisible;

    // 42 = 7 days a week * 7 weeks.
    private DayOfMonth[] _days = new DayOfMonth[42];


    public Month() {
        initComponents();

        _propertyChangeListeners = new ArrayList<PropertyChangeListener>();

        _backButton.setDirection(DirectionButton.Direction.Left);
        _backButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                handleBackClick();
            }
        });
        _forwardButton.setDirection(DirectionButton.Direction.Right);
        _forwardButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                handleForwardClick();
            }
        });

        // We do this so we can loop over it later.
        _days[0] = dayOfMonth1;
        _days[1] = dayOfMonth2;
        _days[2] = dayOfMonth3;
        _days[3] = dayOfMonth4;
        _days[4] = dayOfMonth5;
        _days[5] = dayOfMonth6;
        _days[6] = dayOfMonth7;
        _days[7] = dayOfMonth8;
        _days[8] = dayOfMonth9;
        _days[9] = dayOfMonth10;
        _days[10] = dayOfMonth11;
        _days[11] = dayOfMonth12;
        _days[12] = dayOfMonth13;
        _days[13] = dayOfMonth14;
        _days[14] = dayOfMonth15;
        _days[15] = dayOfMonth16;
        _days[16] = dayOfMonth17;
        _days[17] = dayOfMonth18;
        _days[18] = dayOfMonth19;
        _days[19] = dayOfMonth20;
        _days[20] = dayOfMonth21;
        _days[21] = dayOfMonth22;
        _days[22] = dayOfMonth23;
        _days[23] = dayOfMonth24;
        _days[24] = dayOfMonth25;
        _days[25] = dayOfMonth26;
        _days[26] = dayOfMonth27;
        _days[27] = dayOfMonth28;
        _days[28] = dayOfMonth29;
        _days[29] = dayOfMonth30;
        _days[30] = dayOfMonth31;
        _days[31] = dayOfMonth32;
        _days[32] = dayOfMonth33;
        _days[33] = dayOfMonth34;
        _days[34] = dayOfMonth35;
        _days[35] = dayOfMonth36;
        _days[36] = dayOfMonth37;
        _days[37] = dayOfMonth38;
        _days[38] = dayOfMonth39;
        _days[39] = dayOfMonth40;
        _days[40] = dayOfMonth41;
        _days[41] = dayOfMonth42;

        // Attach our listener to each of the days.
        for (DayOfMonth day : _days) {
            day.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    handleClick(e);
                }
            });
        }

        // Default us to today's date
        _selectedDate = new LocalDate();
        _todayDate = _selectedDate;
        _monthVisible = _selectedDate.getMonthOfYear();
        _yearVisible = _selectedDate.getYear();

        renderMonth(_monthVisible, _yearVisible);
    }


    /**
     * Handle one of the dates getting clicked.
     *
     * @param e (not null) The day that is getting clicked
     */
    private void handleClick(ActionEvent e)
    {
        // Get the actual day clicked on.
        DayOfMonth day = (DayOfMonth)(e.getSource());

        LocalDate oldDate = _selectedDate;

        selectDay(day);

        PropertyChangeEvent event = new PropertyChangeEvent(this, "DATE", oldDate, _selectedDate);

        for (PropertyChangeListener listener : _propertyChangeListeners) {
            listener.propertyChange(event);
        }
    }


    /**
     * Select the given day on the calendar.  This is called by our listener and by a request for a day.
     *
     * @param day (not null) Day to select
     */
    private void selectDay(@NotNull DayOfMonth day)
    {
        logger.debug("Selecting day: " + day.getValue());

        if (day == _selectedWidget) {
            return;
        }

        // Need to unselect the old date
        if (_selectedWidget != null) {
            _selectedWidget.setSelected(false);
        }
        day.setSelected(true);
        _selectedWidget = day;

        _selectedDate = new LocalDate(_yearVisible, _monthVisible, day.getValue());
    }


    /**
     * Handle the user clicking back one month.
     */
    private void handleBackClick()
    {
        if (_monthVisible > 1) {
            renderMonth(_monthVisible-1, _yearVisible);
        }
        else {
            renderMonth(12, _yearVisible-1);
        }

        if (_selectedDate.getMonthOfYear() == _monthVisible && _selectedDate.getYear() == _yearVisible) {
            // Find the widget holding our day
            for (DayOfMonth dayOfMonth : _days) {
                if (dayOfMonth.isEnabled() && dayOfMonth.getValue() == _selectedDate.getDayOfMonth()) {
                    selectDay(dayOfMonth);
                    break;
                }
            }
        }
    }


    public void handleForwardClick()
    {
        if (_monthVisible < 12) {
            renderMonth(_monthVisible+1, _yearVisible);
        }
        else {
            renderMonth(1, _yearVisible+1);
        }

        if (_selectedDate.getMonthOfYear() == _monthVisible && _selectedDate.getYear() == _yearVisible) {
            // Find the widget holding our day
            for (DayOfMonth dayOfMonth : _days) {
                if (dayOfMonth.isEnabled() && dayOfMonth.getValue() == _selectedDate.getDayOfMonth()) {
                    selectDay(dayOfMonth);
                    break;
                }
            }
        }
    }


    /**
     * Return the currently selected date as a local date (ie, no time zone.)
     *
     * @return (not null) The currently selected date, which may not be on the current calendar.
     */
    @NotNull
    public LocalDate getDate()
    {
        return _selectedDate;
    }


    /**
     * This method will basically draw a month.  It will identify if a day is today's date, a weekend, or
     * just a basic day.
     *
     * @param month The month to render (1 - January, 2 - February, ...)
     * @param year The year to render.
     */
    private void renderMonth(int month, int year)
    {
        // If the month and year being shown matches today's month and year then we need to find today's date
        boolean todayVisible = _todayDate.getMonthOfYear() == month && _todayDate.getYear() == year;
        int today = _todayDate.getDayOfMonth();
//
//        boolean selectedVisible = _selectedDate.getMonthOfYear() == month && _selectedDate.getYear() == year;
//        int selected = _selectedDate.getDayOfMonth();

        // Find the first of the month
        LocalDate first = new LocalDate(year, month, 1);

        // Put the Month and Year at the top
        _monthYearLabel.setText(first.toString("MMMMMM yyyy"));

        // Get how many days in the month there are
        int daysInMonth = first.dayOfMonth().getMaximumValue();

        // Find the offset for when the first of the month begins.
        int offset = first.getDayOfWeek();

        int index = 0;

        // Now fill in the days *before* our month, if any
        if (offset > 0) {
            LocalDate last = first.minusDays(1);
            int day = last.getDayOfMonth();

            for (; index<offset; ++index) {
                _days[index].setValue(day--);
                _days[index].setState(DayOfMonth.State.Disabled);
            }
        }

        // This is our actual day counter
        _selectedWidget = null;
        for (int day=1; day<=daysInMonth; ++day) {
            DayOfMonth dayWidget = _days[index];

            dayWidget.setValue(day);

            // Make sure every day is unselected
            dayWidget.setSelected(false);

            if (todayVisible && today == day) {
                // Color today red
                dayWidget.setState(DayOfMonth.State.Today);
            }
            else if ((day + offset)%7 <= 1) {
                // Color weekends blue.
                dayWidget.setState(DayOfMonth.State.Weekend);
            }
            else {
                dayWidget.setState(DayOfMonth.State.Standard);
            }

            ++index;
        }

        // I think it is impossible for there to not be days after the month
        {
            int day = 1;

            for (; index<42; ++index) {
                _days[index].setValue(day++);
                _days[index].setState(DayOfMonth.State.Disabled);
            }
        }

        _monthVisible = month;
        _yearVisible = year;

        // Without a repaint there is sometimes a little background left that shouldn't be there.
        repaint();
    }


    public void addPropertyChangeListener(PropertyChangeListener listener)
    {
        _propertyChangeListeners.add(listener);
    }


    public void setDate(@NotNull LocalDate date) {

        LocalDate oldDate = _selectedDate;

        _selectedDate = date;

        // If they have changed either the month or year then we will need to re-layout everything.
        int newMonth = date.getMonthOfYear();
        int newYear = date.getYear();

        // Check if this changes the month or the year then we need to redraw the panel
        if (newMonth != _monthVisible || newYear != _yearVisible) {
            // Redraw the month

            renderMonth(newMonth, newYear);
        }

        // Find the widget holding our day
        for (DayOfMonth dayOfMonth : _days) {
            if (dayOfMonth.isEnabled() && dayOfMonth.getValue() == date.getDayOfMonth()) {
                selectDay(dayOfMonth);
                break;
            }
        }


        PropertyChangeEvent event = new PropertyChangeEvent(this, "DATE", oldDate, _selectedDate);

        for (PropertyChangeListener listener : _propertyChangeListeners) {
            listener.propertyChange(event);
        }
    }


    private void initComponents() {
        // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
        _backButton = new DirectionButton();
        _monthYearLabel = new JLabel();
        _forwardButton = new DirectionButton();
        label4 = new JLabel();
        label5 = new JLabel();
        label6 = new JLabel();
        label7 = new JLabel();
        label8 = new JLabel();
        label9 = new JLabel();
        label10 = new JLabel();
        dayOfMonth1 = new DayOfMonth();
        dayOfMonth2 = new DayOfMonth();
        dayOfMonth3 = new DayOfMonth();
        dayOfMonth4 = new DayOfMonth();
        dayOfMonth5 = new DayOfMonth();
        dayOfMonth6 = new DayOfMonth();
        dayOfMonth7 = new DayOfMonth();
        dayOfMonth8 = new DayOfMonth();
        dayOfMonth9 = new DayOfMonth();
        dayOfMonth10 = new DayOfMonth();
        dayOfMonth11 = new DayOfMonth();
        dayOfMonth12 = new DayOfMonth();
        dayOfMonth13 = new DayOfMonth();
        dayOfMonth14 = new DayOfMonth();
        dayOfMonth15 = new DayOfMonth();
        dayOfMonth16 = new DayOfMonth();
        dayOfMonth17 = new DayOfMonth();
        dayOfMonth18 = new DayOfMonth();
        dayOfMonth19 = new DayOfMonth();
        dayOfMonth20 = new DayOfMonth();
        dayOfMonth21 = new DayOfMonth();
        dayOfMonth22 = new DayOfMonth();
        dayOfMonth23 = new DayOfMonth();
        dayOfMonth24 = new DayOfMonth();
        dayOfMonth25 = new DayOfMonth();
        dayOfMonth26 = new DayOfMonth();
        dayOfMonth27 = new DayOfMonth();
        dayOfMonth28 = new DayOfMonth();
        dayOfMonth29 = new DayOfMonth();
        dayOfMonth30 = new DayOfMonth();
        dayOfMonth31 = new DayOfMonth();
        dayOfMonth32 = new DayOfMonth();
        dayOfMonth33 = new DayOfMonth();
        dayOfMonth34 = new DayOfMonth();
        dayOfMonth35 = new DayOfMonth();
        dayOfMonth36 = new DayOfMonth();
        dayOfMonth37 = new DayOfMonth();
        dayOfMonth38 = new DayOfMonth();
        dayOfMonth39 = new DayOfMonth();
        dayOfMonth40 = new DayOfMonth();
        dayOfMonth41 = new DayOfMonth();
        dayOfMonth42 = new DayOfMonth();

        //======== this ========
        setLayout(new FormLayout(
                "7*(default)",
                "default, [15dlu,default], 6*(default)"));
        add(_backButton, CC.xy(1, 1));

        //---- _monthYearLabel ----
        _monthYearLabel.setText("Month Year");
        add(_monthYearLabel, CC.xywh(2, 1, 5, 1, CC.CENTER, CC.CENTER));
        add(_forwardButton, CC.xy(7, 1));

        //---- label4 ----
        label4.setText("Sun");
        label4.setFont(label4.getFont().deriveFont(label4.getFont().getSize() - 3f));
        add(label4, CC.xy(1, 2, CC.CENTER, CC.BOTTOM));

        //---- label5 ----
        label5.setText("Mon");
        label5.setFont(label5.getFont().deriveFont(label5.getFont().getSize() - 3f));
        add(label5, CC.xy(2, 2, CC.CENTER, CC.BOTTOM));

        //---- label6 ----
        label6.setText("Tue");
        label6.setFont(label6.getFont().deriveFont(label6.getFont().getSize() - 3f));
        add(label6, CC.xy(3, 2, CC.CENTER, CC.BOTTOM));

        //---- label7 ----
        label7.setText("Wed");
        label7.setFont(label7.getFont().deriveFont(label7.getFont().getSize() - 3f));
        add(label7, CC.xy(4, 2, CC.CENTER, CC.BOTTOM));

        //---- label8 ----
        label8.setText("Thu");
        label8.setFont(label8.getFont().deriveFont(label8.getFont().getSize() - 3f));
        add(label8, CC.xy(5, 2, CC.CENTER, CC.BOTTOM));

        //---- label9 ----
        label9.setText("Fri");
        label9.setFont(label9.getFont().deriveFont(label9.getFont().getSize() - 3f));
        add(label9, CC.xy(6, 2, CC.CENTER, CC.BOTTOM));

        //---- label10 ----
        label10.setText("Sat");
        label10.setFont(label10.getFont().deriveFont(label10.getFont().getSize() - 3f));
        add(label10, CC.xy(7, 2, CC.CENTER, CC.BOTTOM));
        add(dayOfMonth1, CC.xy(1, 3));
        add(dayOfMonth2, CC.xy(2, 3));
        add(dayOfMonth3, CC.xy(3, 3));
        add(dayOfMonth4, CC.xy(4, 3));
        add(dayOfMonth5, CC.xy(5, 3));
        add(dayOfMonth6, CC.xy(6, 3));
        add(dayOfMonth7, CC.xy(7, 3));
        add(dayOfMonth8, CC.xy(1, 4));
        add(dayOfMonth9, CC.xy(2, 4));
        add(dayOfMonth10, CC.xy(3, 4));
        add(dayOfMonth11, CC.xy(4, 4));
        add(dayOfMonth12, CC.xy(5, 4));
        add(dayOfMonth13, CC.xy(6, 4));
        add(dayOfMonth14, CC.xy(7, 4));
        add(dayOfMonth15, CC.xy(1, 5));
        add(dayOfMonth16, CC.xy(2, 5));
        add(dayOfMonth17, CC.xy(3, 5));
        add(dayOfMonth18, CC.xy(4, 5));
        add(dayOfMonth19, CC.xy(5, 5));
        add(dayOfMonth20, CC.xy(6, 5));
        add(dayOfMonth21, CC.xy(7, 5));
        add(dayOfMonth22, CC.xy(1, 6));
        add(dayOfMonth23, CC.xy(2, 6));
        add(dayOfMonth24, CC.xy(3, 6));
        add(dayOfMonth25, CC.xy(4, 6));
        add(dayOfMonth26, CC.xy(5, 6));
        add(dayOfMonth27, CC.xy(6, 6));
        add(dayOfMonth28, CC.xy(7, 6));
        add(dayOfMonth29, CC.xy(1, 7));
        add(dayOfMonth30, CC.xy(2, 7));
        add(dayOfMonth31, CC.xy(3, 7));
        add(dayOfMonth32, CC.xy(4, 7));
        add(dayOfMonth33, CC.xy(5, 7));
        add(dayOfMonth34, CC.xy(6, 7));
        add(dayOfMonth35, CC.xy(7, 7));
        add(dayOfMonth36, CC.xy(1, 8));
        add(dayOfMonth37, CC.xy(2, 8));
        add(dayOfMonth38, CC.xy(3, 8));
        add(dayOfMonth39, CC.xy(4, 8));
        add(dayOfMonth40, CC.xy(5, 8));
        add(dayOfMonth41, CC.xy(6, 8));
        add(dayOfMonth42, CC.xy(7, 8));
        // JFormDesigner - End of component initialization  //GEN-END:initComponents
    }

    // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
    private DirectionButton _backButton;
    private JLabel _monthYearLabel;
    private DirectionButton _forwardButton;
    private JLabel label4;
    private JLabel label5;
    private JLabel label6;
    private JLabel label7;
    private JLabel label8;
    private JLabel label9;
    private JLabel label10;
    private DayOfMonth dayOfMonth1;
    private DayOfMonth dayOfMonth2;
    private DayOfMonth dayOfMonth3;
    private DayOfMonth dayOfMonth4;
    private DayOfMonth dayOfMonth5;
    private DayOfMonth dayOfMonth6;
    private DayOfMonth dayOfMonth7;
    private DayOfMonth dayOfMonth8;
    private DayOfMonth dayOfMonth9;
    private DayOfMonth dayOfMonth10;
    private DayOfMonth dayOfMonth11;
    private DayOfMonth dayOfMonth12;
    private DayOfMonth dayOfMonth13;
    private DayOfMonth dayOfMonth14;
    private DayOfMonth dayOfMonth15;
    private DayOfMonth dayOfMonth16;
    private DayOfMonth dayOfMonth17;
    private DayOfMonth dayOfMonth18;
    private DayOfMonth dayOfMonth19;
    private DayOfMonth dayOfMonth20;
    private DayOfMonth dayOfMonth21;
    private DayOfMonth dayOfMonth22;
    private DayOfMonth dayOfMonth23;
    private DayOfMonth dayOfMonth24;
    private DayOfMonth dayOfMonth25;
    private DayOfMonth dayOfMonth26;
    private DayOfMonth dayOfMonth27;
    private DayOfMonth dayOfMonth28;
    private DayOfMonth dayOfMonth29;
    private DayOfMonth dayOfMonth30;
    private DayOfMonth dayOfMonth31;
    private DayOfMonth dayOfMonth32;
    private DayOfMonth dayOfMonth33;
    private DayOfMonth dayOfMonth34;
    private DayOfMonth dayOfMonth35;
    private DayOfMonth dayOfMonth36;
    private DayOfMonth dayOfMonth37;
    private DayOfMonth dayOfMonth38;
    private DayOfMonth dayOfMonth39;
    private DayOfMonth dayOfMonth40;
    private DayOfMonth dayOfMonth41;
    private DayOfMonth dayOfMonth42;
    // JFormDesigner - End of variables declaration  //GEN-END:variables
}
